NW 22 - SNMP Transports

Networking

Written by: Mike Ritter and Vincent Lubet June 1993

This Technical Note explains how to write an SNMP (Simple Network Management Protocol) Transport. An SNMP Transport is responsible for communicating between the SNMP Manager and a particular network layer. Thus, if you were writing a new network stack for the Macintosh and wanted it to use the SNMP Manager, you would write an SNMP Transport for your network stack.

Topics

* Reading and Writing SNMP Packets

Introduction

It is assumed in this document that you understand MacSNMP, object-oriented programming and the Shared Library Manager. The SNMP Manager is built using the Shared Library Manager and is a set of shared libraries. The SNMP Manager shared library contains the implementation of the base class for an SNMP Transport. Your transport must be a subclass of this SNMP Transport class. For more information on MacSNMP and the Shared Library Manager see the E.T.O. CD ROM.

This Tech Note provides some additional information needed to construct an SNMP Transport for a particular network stack. An SNMP Transport is required to know about all of the specific idiosyncrasies of SNMP for a particular network stack. SNMP was originally defined to run over the TCP stack using UDP. The IETF (Internet Engineering Task Force) has also defined how to run SNMP over three other network stacks: AppleTalk, IPX, and OSI. These methods are documented by the IETF proposed standards: RFC 1419--SNMP over AppleTalk, RFC 1420--SNMP over IPX, and RFC 1418--SNMP over OSI. There is also an informational RFC draft that describes what must be defined to allow SNMP to run over any particular network stack.

Apple provides SNMP transports that support the TCP stack and the AppleTalk stack. The TCP SNMP Transport provides knowledge of IP addresses, listens on the well-known SNMP sockets, sends SNMP packets on UDP, and resolves addresses in dotted notation into IP addresses for sending Traps. (An SNMP Trap is the method that SNMP entities use to send unsolicited warning messages to each other and should not be confused with a Macintosh Operating System Trap.) The AppleTalk SNMP Transport likewise provides knowledge of AppleTalk addresses, listens on the well-known SNMP sockets, sends SNMP packets on DDP, and resolves NBP (Name Binding Protocol) names into AppleTalk addresses for sending Traps. Similarly, a new SNMP Transport would have to provide these same services: understanding the new network addresses, listening for SNMP packets, sending SNMP packets, and resolving stored console addresses to network addresses for sending Traps.

SNMP Transports

Figure 1--An SNMP Transport provides the interface between the SNMP Manager and a particular network stack. Creating an SNMP Transport

The file Transport.h defines the SNMP Transport class. To create an SNMP Transport, you must subclass the SNMP Transport class. The Shared Library Manager allows a run-time link of your SNMP Transport subclass with the base object SNMP Transport provided in the SNMP Manager shared library. The definition of the SNMP Transport object is as follows:

/**********************************************************************
** Class TSNMPTransport 
***********************************************************************/
#define kTSNMPTransportID 	"snmp:mgr$TSNMPTrans"
#define kLIB_TransID  	"snmp:trans$"	// Library ID for an SNMP Transport 

class TSNMPTransport : public TDynamic 
{
public:
	
				TSNMPTransport();
	virtual		~TSNMPTransport();
	virtual Boolean	IsValid() const;		// returns valid or not	
	virtual OSErr 	InitSNMPTransport(	
		TransportTag		aTag,
		TIAddressPtr		aTrapSocketPtr,
		TIAddressPtr		aReqSocketPtr,
		Boolean		ahandlesresolution,
		short			aWritebufsize,
		TransportRWProcPtr	aWriteProcPtr,
		short			aNumofReads,
		short			aReadTIAddrSRCmax,
		short			aReadTIAddrDESTmax,
		short			aReadbufsize,
		TransportRWProcPtr	aReadProcPtr);
		
	virtual void		SNMPWriteDone(SNMPTransportBlockPtr snmpPtr);
	virtual void		SNMPReadDone(SNMPTransportBlockPtr snmpPtr);
	
	TSNMPManagerPrv*	fSNMPManagerPtr;
				
friend TSNMPManagerPrv;

protected:
		Boolean	fValid;
		Boolean	fhandlesresolution;
		short		fNumofReads;
		short		fReadTIAddrSRCmax;
		short		fReadTIAddrDESTmax;
		short		fReadbufsize;
		short		fWritebufsize;

private:
		
		TransportTag		fTag;
		TIAddressPtr		fTrapSocketPtr;
		TIAddressPtr		fReqSocketPtr;
		TransportRWProcPtr	fWriteProcPtr;
		TransportRWProcPtr	fReadProcPtr;
};

To start an SNMP Transport you must instantiate it. For the AppleTalk SNMP Transport, we install a process on the AppleTalk transition queue that tells us when AppleTalk is coming up or going down and instantiate or destroy the Transport as appropriate. For the TCP SNMP Transport, an INIT31 instantiates the Transport and it is never destroyed. For your Transport you must provide the code that instantiates and destroys your Transport.

When a Transport is instantiated the base class constructor is called first. The base class constructor fills in the fSNMPManagerPtr field with a pointer to a class TSNMPManagerPrv, which can be cast to a pointer to the class TSNMPManager defined in TSNMP.h. This pointer can be used to access the members of the TSNMPManager class. The constructor also adds a pointer to your SNMP Transport object to a queue of transports so that it can find you later, and if everything worked, sets the fValid field to true. Your constructor is then called. If the fValid field is not set to true on the entrance to your constructor, you should bail out of the constructor immediately. If your constructor fails for some reason, you should set the fValid field to false. The Shared Library Manager will then clean up the object so that a partially constructed one does not remain.

Initializing an SNMP Transport

After your Transport is constructed it must be initialized. Your transport will not work until InitSNMPTransport() is called. This routine sets all of the fields in the SNMP Transport from the parameter values that you pass in. You must call the inherited InitSNMPTransport member function if you override it. The following fields must be set.

->	TransportTag		aTag
A long that uniquely identifies a transport (analogous to OSType)
->	TIAddressPtr		aTrapSocketPtr 
Opaque address of where the Transport listens for Traps
->	TIAddressPtr		aReqSocketPtr
Opaque address of where the Transport listens for Requests
->	Boolean			ahandlesresolution
True if the transport can send Traps
->	short			aWritebufsize
Maximum size of write buffers
->	TransportRWProcPtr		aWriteProcPtr
Address of the write procedure
->	short			aNumofReads
Maximum number of reads issued at once
->	short			aReadTIAddrSRCmax
Maximum length of source address for read operations
->	short			aReadTIAddrDESTmax
Maximum length of destination address for read operations
->	short			aReadbufsize
Maximum size of a read buffer
->	TransportRWProcPtr		aReadProcPtr
Address of the read procedure

The aReadProcPtr and aWriteProcPtr have the following definition:

typedef void (*TransportRWProcPtr)(SNMPTransportBlockPtr dataPtr);	

It is recommended that the TransportTag be four human readable ASCII characters that describe the network layer that the Transport talks to. If multiple Transports with the same TransportTag are instantiated, the SNMP Manager will ignore all but the first one instantiated. For the AppleTalk SNMP Transport the tag is 'DDP ', for the TCP SNMP Transport the tag is 'UDP '. This value is used in the Trap Table in the Macintosh Agent to determine which Transport understands the console address stored in a particular row of the table. The sockets are where the Transport listens for Traps and Requests. The SNMP Manager does not understand the format of these addresses and just passes them along to your Transport. They are also used as the source of Traps or Responses sent. Finally, they allow the SNMP Manager to determine what type of packet it is decoding before parsing the raw ASN.1 data. In our Transports we have stored pointers to the actual bits of the network layer addresses in these fields. The aWritebufsize and aReadbufsize are the size of buffers that the SNMP Manager allocates for your Transport to use.

Reading SNMP Packets

Finally, InitSNMPTransport() tries to issue aNumofReads outstanding reads by calling your aReadProc() with a filled in SNMPTransportBlock as shown in Figure 2. The SNMPTransportBlock is defined as follows:

struct SNMPTransportBlock {
	unsigned long		qLink;		// reserved for pointer to next block
	short			qType;		// reserved for queue routines
	TSNMPTransport* 	transport;	// who was asked to read or write block
	SNMPError		result;	// after request is serviced
	void* 			destopaqueData;// destination address to be resolved 							(used in write trap only)
	TIAddressPtr		destination;	// who the packet was sent to 
	TIAddressPtr		source;	// who sent the packet to us
	void *			UserDataPtr;	// Transport work space
	Boolean		freeFlag;	// is the write finished?
	Boolean		readFlag;	// managed by SNMP Manager
	PacketElementPtr	pktelementPtr;// managed by SNMP Manager
	SNMPPacketStructPtr	packetPtr;	// managed by SNMP Manager
};

The aReadProc() must be able to catch both incoming Traps and Requests. It must also be able to be called at any time, thus you must not allocate memory using the normal Macintosh memory calls. You may use the area pointed to by UserDataPtr for any scratch you might need. The SNMP Manager has preallocated this area for you according to the sizes you set in aReadbufsize. It must also return immediately. When a read actually completes you must call SNMPReadDone() after filling in the data, the source and destination addresses, the actual number of bytes read (in packetPtr->packetPiece.dataSize), the freeFlag (set to false), and the result (snmpNoError if it worked.)

Warning: If the freeFlag is incorrectly set, the SNMP Manager will become hopelessly confused.

After the read packet is processed by the SNMP Manager it issues another read call to aReadProc() so that there will always be some number of outstanding reads. If there are no outstanding reads at any time it is allowable for your transport to drop SNMP packets.

Figure 2--Layout of SNMPTransportBlock when aReadProc() called. Only the fields that the Transport may need to access are shown.

When your Transport is deleted you must ensure that your completion routines will not be called after your destructor is finished and your Transport object is gone. You may have to wait for asynchronous writes to complete and cancel any outstanding reads. The base class destructor will ensure that any packets you have queued up for processing will be thrown away.

Figure 3--Layout of SNMPTransportBlock when the SNMP Manager calls aWriteProc() with a response to an SNMP Request.* *Only the fields that the Transport may need to access are shown.

Writing SNMP Packets

After a packet is processed by the SNMP Manager, it will almost always generate a Response packet. The SNMP Manager will either respond to a Request or generate a Trap. In the cases of a simple Response the SNMP Manager will call your aWriteProc() with an SNMPTransportBlock filled out as shown in Figure 3. Your aWriteProc() may be called at any time, thus you must not allocate memory using the normal Macintosh memory calls. You may use the area pointed to by UserDatPtr for any scratch memory you need. The SNMP Manager has preallocated this area for you according to the sizes you set in aWritebufsize. The destination socket is the same block as was passed in on the read as the source socket, the source socket is your initialized fRequestSocketPtr, the packetPtr points to the SNMP data to put on the wire. You must return immediately from the call to aWriteProc(). After the write completes you should fill in the result, set the freeFlag to true, and call SNMPWriteDone() so that the SNMP Manager can free up the memory it allocated.

Warning: If the freeFlag is incorrectly set the SNMP Manager will become hopelessly confused.

If your Transport can send Traps it must set the fHandlesResolution field to true. It is strongly encouraged that your transport handle Traps, if it did not, it would not be fully compliant with the SNMP standard. When a Trap is generated, the SNMP Manager will determine if the Trap is supposed to be sent to a console that supports your Transport type by looking through the Trap table in the Macintosh System MIB implemented by the Macintosh Agent. If so, it will issue a call to your aWriteProc() as above, except that the destOpaqueData will point to a block that the SNMP Manager has procured from the trapDestination field in the Trap Table in the Macintosh Agent. It is up to you to define how this address will be stored. However, it must be nonvolatile between reboots of the system. For the AppleTalk SNMP transport, we store the NBP name of the console as specified in the AppleTalk over SNMP RFC. For the TCP SNMP Transport, we store the IP address of the console in dotted notation The Transport is responsible for turning this address into the wire address of the console. Everything else is as above. You must return immediately after the call to your aWriteProc() and call SNMPWriteDone() when the Trap write completes.

The Trap Table is implemented by the Macintosh Agent and is defined in ASN.1 by the Macintosh System MIB as:

TrapRequestEntry 	::= SEQUENCE {
trapIndex		INTEGER,			// unsigned long
trapCommunity		MacintoshDisplayString,	// opaque data 
trapProtocol		MacOSType,			// four bytes
trapDestination	OCTET STRING,			// opaque data
trapValidity		INTEGER			// 1 = valid, 2 = invalid
}

The two fields that an SNMP Transport author must define are the trapProtocol and the trapDestination. The trapProtocol is compared by the SNMP Manager to the TransportTag and is used to identify which SNMP Transport will be able to resolve the trapDestination entry and send the Trap. The trapProtocol field must contain the same value that was initialized in the transportTag field. Some care should be given to the format of the trapDestination as network managers will have to enter these addresses by hand from a console.

Summary

An SNMP Transport provides an interface between the SNMP Manager and a network layer of a particular network stack. It must be able to be called at any time, thus cannot depend on the Macintosh memory calls. A Transport must understand how a Trap destination (console address) is to be stored in the Macintosh Agent's Trap Table. This address format must be stable between reboots of the system and must be resolvable into a network address for the console. It is the responsibility of the Transport developer to inform network managers of how to store this address. For IPX, AppleTalk, and OSI, these standards have been specified in RFCs (Request For Comments). These documents are available on-line off of the Internet and are maintained in various repositories and formats by the IETF.

Further Reference: